1use super::crypto::*;
3use crate::ext::io::*;
4use crate::scripts::base::*;
5use crate::types::*;
6use crate::utils::encoding::{decode_to_string, encode_string};
7use anyhow::Result;
8use std::collections::HashMap;
9use std::ffi::CString;
10use std::io::{Read, Seek, SeekFrom, Write};
11use std::sync::{Arc, Mutex};
12
13#[derive(Debug)]
14pub struct EscudeBinArchiveBuilder {}
16
17impl EscudeBinArchiveBuilder {
18 pub const fn new() -> Self {
20 EscudeBinArchiveBuilder {}
21 }
22}
23
24impl ScriptBuilder for EscudeBinArchiveBuilder {
25 fn default_encoding(&self) -> Encoding {
26 Encoding::Cp932
27 }
28
29 fn default_archive_encoding(&self) -> Option<Encoding> {
30 Some(Encoding::Cp932)
31 }
32
33 fn build_script(
34 &self,
35 data: Vec<u8>,
36 _filename: &str,
37 _encoding: Encoding,
38 archive_encoding: Encoding,
39 config: &ExtraConfig,
40 _archive: Option<&Box<dyn Script>>,
41 ) -> Result<Box<dyn Script + Send + Sync>> {
42 Ok(Box::new(EscudeBinArchive::new(
43 MemReader::new(data),
44 archive_encoding,
45 config,
46 )?))
47 }
48
49 fn build_script_from_file(
50 &self,
51 filename: &str,
52 _encoding: Encoding,
53 archive_encoding: Encoding,
54 config: &ExtraConfig,
55 _archive: Option<&Box<dyn Script>>,
56 ) -> Result<Box<dyn Script + Send + Sync>> {
57 if filename == "-" {
58 let data = crate::utils::files::read_file(filename)?;
59 Ok(Box::new(EscudeBinArchive::new(
60 MemReader::new(data),
61 archive_encoding,
62 config,
63 )?))
64 } else {
65 let f = std::fs::File::open(filename)?;
66 let reader = std::io::BufReader::new(f);
67 Ok(Box::new(EscudeBinArchive::new(
68 reader,
69 archive_encoding,
70 config,
71 )?))
72 }
73 }
74
75 fn build_script_from_reader<'a>(
76 &self,
77 reader: Box<dyn ReadSeek + Send + Sync + 'a>,
78 _filename: &str,
79 _encoding: Encoding,
80 archive_encoding: Encoding,
81 config: &ExtraConfig,
82 _archive: Option<&Box<dyn Script>>,
83 ) -> Result<Box<dyn Script + Send + Sync + 'a>> {
84 Ok(Box::new(EscudeBinArchive::new(
85 reader,
86 archive_encoding,
87 config,
88 )?))
89 }
90
91 fn extensions(&self) -> &'static [&'static str] {
92 &["bin"]
93 }
94
95 fn script_type(&self) -> &'static ScriptType {
96 &ScriptType::EscudeArc
97 }
98
99 fn is_this_format(&self, _filename: &str, buf: &[u8], buf_len: usize) -> Option<u8> {
100 if buf_len > 8 && buf.starts_with(b"ESC-ARC2") {
101 return Some(255);
102 }
103 None
104 }
105
106 fn is_archive(&self) -> bool {
107 true
108 }
109
110 fn create_archive(
111 &self,
112 filename: &str,
113 files: &[&str],
114 encoding: Encoding,
115 config: &ExtraConfig,
116 ) -> Result<Box<dyn Archive>> {
117 let f = std::fs::File::create(filename)?;
118 let writer = std::io::BufWriter::new(f);
119 let archive = EscudeBinArchiveWriter::new(writer, files, encoding, config)?;
120 Ok(Box::new(archive))
121 }
122}
123
124#[derive(Debug)]
125struct BinEntry {
126 name_offset: u32,
127 data_offset: u32,
128 length: u32,
129}
130
131struct Entry {
132 name: String,
133 data: MemReader,
134}
135
136impl Read for Entry {
137 fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
138 self.data.read(buf)
139 }
140}
141
142impl ArchiveContent for Entry {
143 fn name(&self) -> &str {
144 &self.name
145 }
146
147 fn size(&self) -> Option<u64> {
148 Some(self.data.data.len() as u64)
149 }
150
151 fn script_type(&self) -> Option<&ScriptType> {
152 if self.data.data.starts_with(b"ESCR1_00") {
153 Some(&ScriptType::Escude)
154 } else if self.data.data.starts_with(b"LIST") {
155 Some(&ScriptType::EscudeList)
156 } else {
157 None
158 }
159 }
160
161 fn data(&mut self) -> Result<Vec<u8>> {
162 Ok(self.data.data.clone())
163 }
164
165 fn to_data<'a>(&'a mut self) -> Result<Box<dyn ReadSeek + Send + Sync + 'a>> {
166 Ok(Box::new(&mut self.data))
167 }
168}
169
170#[derive(Debug)]
171pub struct EscudeBinArchive<'b, T: Read + Seek + std::fmt::Debug + 'b> {
173 reader: Arc<Mutex<T>>,
174 file_count: u32,
175 entries: Vec<BinEntry>,
176 archive_encoding: Encoding,
177 _mark: std::marker::PhantomData<&'b ()>,
178}
179
180impl<'b, T: Read + Seek + std::fmt::Debug + 'b> EscudeBinArchive<'b, T> {
181 pub fn new(mut reader: T, archive_encoding: Encoding, _config: &ExtraConfig) -> Result<Self> {
187 let mut header = [0u8; 8];
188 reader.read_exact(&mut header)?;
189 if &header != b"ESC-ARC2" {
190 return Err(anyhow::anyhow!("Invalid Escude binary script header"));
191 }
192 reader.seek(SeekFrom::Start(0xC))?;
193 let mut crypto_reader = CryptoReader::new(&mut reader)?;
194 let file_count = crypto_reader.read_u32()?;
195 let _name_tbl_len = crypto_reader.read_u32()?;
196 let mut entries = Vec::with_capacity(file_count as usize);
197 for _ in 0..file_count {
198 let name_offset = crypto_reader.read_u32()?;
199 let data_offset = crypto_reader.read_u32()?;
200 let length = crypto_reader.read_u32()?;
201 entries.push(BinEntry {
202 name_offset,
203 data_offset,
204 length,
205 });
206 }
207 Ok(EscudeBinArchive {
208 reader: Arc::new(Mutex::new(reader)),
209 file_count,
210 entries,
211 archive_encoding,
212 _mark: std::marker::PhantomData,
213 })
214 }
215}
216
217impl<'b, T: Read + Seek + std::fmt::Debug + 'b> Script for EscudeBinArchive<'b, T> {
218 fn default_output_script_type(&self) -> OutputScriptType {
219 OutputScriptType::Json
220 }
221
222 fn default_format_type(&self) -> FormatOptions {
223 FormatOptions::None
224 }
225
226 fn is_archive(&self) -> bool {
227 true
228 }
229
230 fn iter_archive_filename<'a>(
231 &'a self,
232 ) -> Result<Box<dyn Iterator<Item = Result<String>> + 'a>> {
233 Ok(Box::new(EscudeBinArchiveIter {
234 entries: self.entries.iter(),
235 reader: self.reader.clone(),
236 file_count: self.file_count,
237 archive_encoding: self.archive_encoding,
238 }))
239 }
240
241 fn iter_archive_offset<'a>(&'a self) -> Result<Box<dyn Iterator<Item = Result<u64>> + 'a>> {
242 Ok(Box::new(
243 self.entries.iter().map(|e| Ok(e.data_offset as u64)),
244 ))
245 }
246
247 fn open_file<'a>(&'a self, index: usize) -> Result<Box<dyn ArchiveContent + Send + Sync + 'a>> {
248 if index >= self.entries.len() {
249 return Err(anyhow::anyhow!(
250 "Index out of bounds: {} (max: {})",
251 index,
252 self.entries.len()
253 ));
254 }
255 let entry = &self.entries[index];
256 let name = self
257 .reader
258 .cpeek_cstring_at(entry.name_offset as u64 + self.file_count as u64 * 12 + 0x14)?;
259 let name = decode_to_string(self.archive_encoding, name.as_bytes(), true)?;
260 let mut data = self
261 .reader
262 .cpeek_at_vec(entry.data_offset as u64, entry.length as usize)?;
263 if data.starts_with(b"acp") {
264 let mut decoder = match super::lzw::LZWDecoder::new(&data) {
265 Ok(decoder) => decoder,
266 Err(e) => return Err(anyhow::anyhow!("Failed to create LZW decoder: {}", e)),
267 };
268 data = decoder.unpack()?;
269 }
270 Ok(Box::new(Entry {
271 name,
272 data: MemReader::new(data),
273 }))
274 }
275}
276
277struct EscudeBinArchiveIter<'a, T: Iterator<Item = &'a BinEntry>, R: Read + Seek> {
278 entries: T,
279 reader: Arc<Mutex<R>>,
280 file_count: u32,
281 archive_encoding: Encoding,
282}
283
284impl<'a, T: Iterator<Item = &'a BinEntry>, R: Read + Seek> Iterator
285 for EscudeBinArchiveIter<'a, T, R>
286{
287 type Item = Result<String>;
288
289 fn next(&mut self) -> Option<Self::Item> {
290 let entry = match self.entries.next() {
291 Some(entry) => entry,
292 None => return None,
293 };
294 let name_offset = entry.name_offset as u64 + self.file_count as u64 * 12 + 0x14;
295 let name = match self.reader.cpeek_cstring_at(name_offset) {
296 Ok(name) => name,
297 Err(e) => return Some(Err(e.into())),
298 };
299 let name = match decode_to_string(self.archive_encoding, name.as_bytes(), true) {
300 Ok(name) => name,
301 Err(e) => return Some(Err(e.into())),
302 };
303 Some(Ok(name))
304 }
305}
306
307pub struct EscudeBinArchiveWriter<T: Write + Seek> {
309 writer: T,
310 headers: HashMap<String, BinEntry>,
311 name_tbl_len: u32,
312 fake: bool,
313}
314
315impl<T: Write + Seek> EscudeBinArchiveWriter<T> {
316 pub fn new(
323 mut writer: T,
324 files: &[&str],
325 encoding: Encoding,
326 config: &ExtraConfig,
327 ) -> Result<Self> {
328 writer.write_all(b"ESC-ARC2")?;
329 let header_len = 0xC + 0xC * files.len();
330 let header = vec![0u8; header_len];
331 writer.write_all(&header)?;
332 let mut headers = HashMap::new();
333 for file in files {
334 let f = file.to_string();
335 let encoded = encode_string(encoding, file, false)?;
336 let encoded = CString::new(encoded)?;
337 let name_offset = writer.stream_position()? as u32;
338 writer.write_all(encoded.as_bytes_with_nul())?;
339 headers.insert(
340 f,
341 BinEntry {
342 name_offset,
343 data_offset: 0,
344 length: 0,
345 },
346 );
347 }
348 let name_tbl_len = writer.stream_position()? as u32 - header_len as u32 - 0x8;
349 Ok(EscudeBinArchiveWriter {
350 writer,
351 headers,
352 name_tbl_len,
353 fake: config.escude_fake_compress,
354 })
355 }
356}
357
358impl<T: Write + Seek> Archive for EscudeBinArchiveWriter<T> {
359 fn new_file<'a>(
360 &'a mut self,
361 name: &str,
362 _size: Option<u64>,
363 ) -> Result<Box<dyn WriteSeek + 'a>> {
364 let entry = self
365 .headers
366 .get_mut(name)
367 .ok_or_else(|| anyhow::anyhow!("File '{}' not found in archive", name))?;
368 if entry.data_offset != 0 {
369 return Err(anyhow::anyhow!("File '{}' already exists in archive", name));
370 }
371 entry.data_offset = self.writer.stream_position()? as u32;
372 Ok(Box::new(EscudeBinArchiveFileWithLzw::new(
373 entry,
374 &mut self.writer,
375 self.fake,
376 )?))
377 }
378
379 fn write_header(&mut self) -> Result<()> {
380 self.writer.seek(SeekFrom::Start(0x8))?;
381 let mut crypto = CryptoWriter::new(&mut self.writer)?;
382 let file_count = self.headers.len() as u32;
383 crypto.write_u32(file_count)?;
384 crypto.write_u32(self.name_tbl_len)?;
385 let mut entries: Vec<_> = self.headers.values().collect();
386 entries.sort_by(|a, b| a.name_offset.cmp(&b.name_offset));
387 for entry in entries {
388 let name_offset = entry.name_offset - file_count * 12 - 0x14;
389 crypto.write_u32(name_offset)?;
390 crypto.write_u32(entry.data_offset)?;
391 crypto.write_u32(entry.length)?;
392 }
393 Ok(())
394 }
395}
396
397pub struct EscudeBinArchiveFileWithLzw<'a, T: Write + Seek> {
399 writer: EscudeBinArchiveFile<'a, T>,
400 buf: MemWriter,
401 fake: bool,
402}
403
404impl<'a, T: Write + Seek> EscudeBinArchiveFileWithLzw<'a, T> {
405 fn new(header: &'a mut BinEntry, writer: &'a mut T, fake: bool) -> Result<Self> {
406 let writer = EscudeBinArchiveFile {
407 header,
408 writer,
409 pos: 0,
410 };
411 Ok(EscudeBinArchiveFileWithLzw {
412 writer,
413 buf: MemWriter::new(),
414 fake,
415 })
416 }
417}
418
419impl<'a, T: Write + Seek> Write for EscudeBinArchiveFileWithLzw<'a, T> {
420 fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
421 self.buf.write(buf)
422 }
423
424 fn flush(&mut self) -> std::io::Result<()> {
425 self.buf.flush()
426 }
427}
428
429impl<'a, T: Write + Seek> Seek for EscudeBinArchiveFileWithLzw<'a, T> {
430 fn seek(&mut self, pos: SeekFrom) -> std::io::Result<u64> {
431 self.buf.seek(pos)
432 }
433
434 fn stream_position(&mut self) -> std::io::Result<u64> {
435 self.buf.stream_position()
436 }
437
438 fn rewind(&mut self) -> std::io::Result<()> {
439 self.buf.rewind()
440 }
441}
442
443impl<'a, T: Write + Seek> Drop for EscudeBinArchiveFileWithLzw<'a, T> {
444 fn drop(&mut self) {
445 let buf = self.buf.as_slice();
446 let encoder = super::lzw::LZWEncoder::new();
447 let data = match encoder.encode(buf, self.fake) {
448 Ok(data) => data,
449 Err(e) => {
450 eprintln!("Failed to encode LZW data: {}", e);
451 crate::COUNTER.inc_error();
452 return;
453 }
454 };
455 match self.writer.write_all(&data) {
456 Ok(_) => {}
457 Err(e) => {
458 eprintln!("Failed to write LZW data: {}", e);
459 crate::COUNTER.inc_error();
460 }
461 }
462 }
463}
464
465pub struct EscudeBinArchiveFile<'a, T: Write + Seek> {
467 header: &'a mut BinEntry,
468 writer: &'a mut T,
469 pos: usize,
470}
471
472impl<'a, T: Write + Seek> Write for EscudeBinArchiveFile<'a, T> {
473 fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
474 self.writer.seek(SeekFrom::Start(
475 self.header.data_offset as u64 + self.pos as u64,
476 ))?;
477 let written = self.writer.write(buf)?;
478 self.pos += written;
479 self.header.length = self.header.length.max(self.pos as u32);
480 Ok(written)
481 }
482
483 fn flush(&mut self) -> std::io::Result<()> {
484 self.writer.flush()
485 }
486}
487
488impl<'a, T: Write + Seek> Seek for EscudeBinArchiveFile<'a, T> {
489 fn seek(&mut self, pos: SeekFrom) -> std::io::Result<u64> {
490 let new_pos = match pos {
491 SeekFrom::Start(offset) => offset as usize,
492 SeekFrom::End(offset) => {
493 if offset < 0 {
494 if (-offset) as usize > self.header.length as usize {
495 return Err(std::io::Error::new(
496 std::io::ErrorKind::InvalidInput,
497 "Seek from end exceeds file length",
498 ));
499 }
500 self.header.length as usize - (-offset) as usize
501 } else {
502 self.header.length as usize + offset as usize
503 }
504 }
505 SeekFrom::Current(offset) => {
506 if offset < 0 {
507 if (-offset) as usize > self.pos {
508 return Err(std::io::Error::new(
509 std::io::ErrorKind::InvalidInput,
510 "Seek from current exceeds current position",
511 ));
512 }
513 self.pos.saturating_sub((-offset) as usize)
514 } else {
515 self.pos + offset as usize
516 }
517 }
518 };
519 self.pos = new_pos;
520 Ok(self.pos as u64)
521 }
522}